#include "StatisticsTabController.h"
#include <QQuickWindow>
#include "../overlaycontroller.h"

// application namespace
namespace advsettings {

void StatisticsTabController::initStage1() {
}

void StatisticsTabController::initStage2(OverlayController * parent, QQuickWindow * widget) {
	this->parent = parent;
	this->widget = widget;
}

void StatisticsTabController::eventLoopTick(vr::TrackedDevicePose_t* devicePoses, float leftSpeed, float rightSpeed) {
	vr::Compositor_CumulativeStats pStats;
	vr::VRCompositor()->GetCumulativeStats(&pStats, sizeof(vr::Compositor_CumulativeStats));
	if (pStats.m_nPid != m_cumStats.m_nPid) {
		m_cumStats = pStats;
		m_droppedFramesOffset = 0;
		m_reprojectedFramesOffset = 0;
		m_timedOutOffset = 0;
		m_totalRatioPresentedOffset = 0;
		m_totalRatioReprojectedOffset = 0;
	}
	m_cumStats = pStats;

	auto& m = devicePoses->mDeviceToAbsoluteTracking.m;

	// Hmd Distance //
	if (lastPosTimer == 0) {
		if (devicePoses[0].bPoseIsValid && devicePoses[0].eTrackingResult == vr::TrackingResult_Running_OK) {
			if (!lastHmdPosValid) {
				lastHmdPosValid = true;
			} else {
				auto delta = std::sqrt(std::pow(m[0][3] - lastHmdPos[0], 2) /*+ std::pow(m[1][3] - lastHmdPos[1], 2)*/ + std::pow(m[2][3] - lastHmdPos[2], 2));
				if (delta >= 0.01f) {
					m_hmdDistanceMoved += delta;
				}
			}
			lastHmdPos[0] = m[0][3];
			lastHmdPos[1] = m[1][3];
			lastHmdPos[2] = m[2][3];
		} else {
			lastHmdPosValid = false;
		}
	}

	// Controller speeds //
	if (leftSpeed > m_leftControllerMaxSpeed) {
		m_leftControllerMaxSpeed = leftSpeed;
	}
	if (rightSpeed > m_rightControllerMaxSpeed) {
		m_rightControllerMaxSpeed = rightSpeed;
	}

	// Hmd Rotation //
	/*
	| Intrinsic y-x'-z" rotation matrix:
	| cr*cy+sp*sr*sy | cr*sp*sy-cy*sr | cp*sy |
	| cp*sr          | cp*cr          |-sp    |
	| cy*sp*sr-cr*sy | cr*cy*sp+sr*sy | cp*cy |

	yaw = atan2(cp*sy, cp*cy) [pi, -pi], CCW
	pitch = -asin(-sp) [pi/2, -pi/2]
	roll = atan2(cp*sr, cp*cr) [pi, -pi], CW
	*/

	double yawRaw = 0.0;
	double yaw = 0.0;
	bool rotationChanged = false;
	if (devicePoses[0].bPoseIsValid && devicePoses[0].eTrackingResult == vr::TrackingResult_Running_OK) {
		yawRaw = std::atan2(m[0][2], m[2][2]);
		if (yawRaw < 0.0f) {
			yawRaw += 2 * M_PI; // map to [0, 2*pi], CCW
		}
		yaw = yawRaw - rotationOffset;
		if (yaw < 0.0) {
			yaw = 2 * M_PI + yaw;
		}
		if (rotationResetFlag) {
			rotationDir = 0;
			rotationCounter = 0;
			rotationOffset = yawRaw;
			rotationResetFlag = false;
			lastYaw = -1.0f;
			rotationChanged = true;
		} else if (lastYaw < 0.0f && yaw > 0.0) {
			lastYaw = yaw;
			rotationChanged = true;
			if (yaw <= M_PI) {
				rotationDir = 1;
			} else {
				rotationDir = -1;
			}
		} else if (std::abs(lastYaw - yaw) >= 0.01 && yaw > 0.0) {
			auto diff = yaw - lastYaw;
			int mode = 0;
			if (std::abs(diff) > M_PI) {
				if (diff < -M_PI) { // CCW overflow
					mode = 1;
				} else if (diff > M_PI) { // CW overflow
					mode = -1;
				}
			} else {
				if (lastYaw < 0.0 && yaw > 0.0) {
					mode = 1;
				} else if (lastYaw > 0.0 && yaw < 0.0) {
					mode = -1;
				}
			}
			if (mode > 0) {
				if (rotationCounter == 0 && rotationDir <= 0) {
					rotationDir = 1;
				} else {
					rotationCounter++;
				}
			} else if (mode < 0) {
				if (rotationCounter == 0 && rotationDir >= 0) {
					rotationDir = -1;
				} else {
					rotationCounter--;
				}
			}
			lastYaw = yaw;
			rotationChanged = true;
		}
	} else {
		lastYaw = -1;
	}

	if (rotationChanged) {
		m_hmdRotation = (float)rotationCounter;
		if (rotationDir > 0) {
			m_hmdRotation += yaw / (2 * M_PI);
		} else if (rotationDir < 0) {
			m_hmdRotation += -1.0f + yaw / (2 * M_PI);
		}
	}
	if (lastPosTimer == 0) {
		lastPosTimer = 10;
	} else {
		lastPosTimer--;
	}
}

float StatisticsTabController::hmdDistanceMoved() const {
	return m_hmdDistanceMoved;
}

float StatisticsTabController::hmdRotations() const {
	return m_hmdRotation;
}

float StatisticsTabController::rightControllerMaxSpeed() const {
	return m_rightControllerMaxSpeed;
}

float StatisticsTabController::leftControllerMaxSpeed() const {
	return m_leftControllerMaxSpeed;
}

unsigned StatisticsTabController::presentedFrames() const {
	return m_cumStats.m_nNumFramePresents - m_presentedFramesOffset;
}

unsigned StatisticsTabController::droppedFrames() const {
	return m_cumStats.m_nNumDroppedFrames - m_droppedFramesOffset;
}

unsigned StatisticsTabController::reprojectedFrames() const {
	return m_cumStats.m_nNumReprojectedFrames - m_reprojectedFramesOffset;
}

unsigned StatisticsTabController::timedOut() const {
	return m_cumStats.m_nNumTimedOut - m_timedOutOffset;
}

float StatisticsTabController::totalReprojectedRatio() const {
	float totalFrames = (float)(m_cumStats.m_nNumFramePresents - m_totalRatioPresentedOffset);
	float reprojectedFrames = (float)(m_cumStats.m_nNumReprojectedFrames - m_totalRatioReprojectedOffset);
	if (totalFrames != 0.0f) {
		return reprojectedFrames / totalFrames;
	} else {
		return 0.0;
	}
}

void StatisticsTabController::statsDistanceResetClicked() {
	lastHmdPosValid = false;
	if (m_hmdDistanceMoved != 0.0) {
		m_hmdDistanceMoved = 0.0;
	}
}

void StatisticsTabController::statsRotationResetClicked() {
	rotationResetFlag = true;
}

void StatisticsTabController::statsLeftControllerSpeedResetClicked() {
	if (m_leftControllerMaxSpeed != 0.0) {
		m_leftControllerMaxSpeed = 0.0;
	}
}

void StatisticsTabController::statsRightControllerSpeedResetClicked() {
	if (m_rightControllerMaxSpeed != 0.0) {
		m_rightControllerMaxSpeed = 0.0;
	}
}

void StatisticsTabController::presentedFramesResetClicked() {
	m_presentedFramesOffset = m_cumStats.m_nNumFramePresents;
}

void StatisticsTabController::droppedFramesResetClicked() {
	m_droppedFramesOffset = m_cumStats.m_nNumDroppedFrames;
}

void StatisticsTabController::reprojectedFramesResetClicked() {
	m_reprojectedFramesOffset = m_cumStats.m_nNumReprojectedFrames;
}

void StatisticsTabController::timedOutResetClicked() {
	m_timedOutOffset = m_cumStats.m_nNumTimedOut;
}

void StatisticsTabController::totalRatioResetClicked() {
	m_totalRatioPresentedOffset = m_cumStats.m_nNumFramePresents;
	m_totalRatioReprojectedOffset = m_cumStats.m_nNumReprojectedFrames;
}

} // namespace advconfig
